﻿namespace Cellfish.Toolbox.Facts.Collections.Concurrent
{
    using System;
    using System.Collections.Generic;

    using Cellfish.Test;
    using Cellfish.Toolbox.Collections.Concurrent;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class ConcurrentLinkedListFacts
    {
        [TestMethod]
        public void When_creating_ConcurrentLinkedList_with_items_Then_list_have_items()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711, 42 });
            Assert.AreEqual(2, list.Count, "Wrong count number");
            Assert.AreEqual(4711, list.First, "First element is wrong");
            Assert.AreEqual(42, list.Last, "Last element is wrong.");
            StringAssert.StartsWith(list.ToString(), "2 items", "ToString does not reflect number of items in list");
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_comparing_with_null_Then_false_is_returned()
        {
            var list = new ConcurrentLinkedList<int>();
            Assert.IsFalse(list.Equals(null));
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_comparing_with_object_of_different_type_Then_false_is_returned()
        {
            var list = new ConcurrentLinkedList<int>();
            Assert.IsFalse(list.Equals(new int[0]));
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_comparing_with_self_Then_true_is_returned()
        {
            var list = new ConcurrentLinkedList<int>();
            Assert.IsTrue(list.Equals(list));
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_comparing_with_other_ConcurrentLinkedList_Then_false_is_returned()
        {
            var list = new ConcurrentLinkedList<int>();
            Assert.IsFalse(list.Equals(new ConcurrentLinkedList<int>()));
        }

        [TestMethod]
        public void
            Given_empty_ConcurrentLinkedList_with_value_type_When_getting_first_and_last_item_Then_default_value_is_returned
            ()
        {
            var list = new ConcurrentLinkedList<int>();
            Assert.AreEqual(default(int), list.First, "First is wrong");
            Assert.AreEqual(default(int), list.Last, "Last is wrong");
        }

        [TestMethod]
        public void
            Given_empty_ConcurrentLinkedList_with_reference_type_When_getting_first_and_last_item_Then_default_value_is_returned
            ()
        {
            var list = new ConcurrentLinkedList<object>();
            Assert.IsNull(list.First, "First is wrong");
            Assert.IsNull(list.Last, "Last is wrong");
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_With_one_item_When_adding_item_first_Then_old_item_is_last()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711 });
            list.AddFirst(42);
            Assert.AreEqual(42, list.First, "First is wrong");
            Assert.AreEqual(4711, list.Last, "Last is wrong");
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_with_one_item_When_adding_item_last_Then_old_item_is_first()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711 });
            list.AddLast(42);
            Assert.AreEqual(4711, list.First, "First is wrong");
            Assert.AreEqual(42, list.Last, "Last is wrong");
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_with_items_When_clearing_Then_no_items_are_in_list()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711 });
            list.Clear();
            Ensure.Empty(list);
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_calling_contains_with_item_not_in_list_Then_false_is_returned()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711 });
            Assert.IsFalse(list.Contains(42));
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_calling_contains_with_item_in_list_Then_true_is_returned()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 42 });
            Assert.IsTrue(list.Contains(42));
        }

        [TestMethod]
        public void
            Given_ConcurrentLinkedList_When_removing_item_not_in_list_Then_false_is_returned_and_list_is_unchanged()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711 });
            Assert.IsFalse(list.Remove(42));
            Assert.AreEqual(1, list.Count, "There should be no items in list");
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_removing_item_in_list_Then_true_is_returned_and_list_is_changed()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 42 });
            Assert.IsTrue(list.Remove(42));
            Ensure.Empty(list, "List should be empty.");
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_with_two_items_When_removing_first_item_Then_only_last_item_is_still_in_list()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 42, 4711 });
            list.RemoveFirst();
            Assert.AreEqual(1, list.Count, "There should be one item in list");
            Assert.AreEqual(4711, list.First, "First item is wrong");
            Assert.AreEqual(4711, list.Last, "Last item is wrong");
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_with_two_items_When_removing_last_item_Then_only_first_item_is_still_in_list()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711, 42 });
            list.RemoveLast();
            Assert.AreEqual(1, list.Count, "There should be one item in list");
            Assert.AreEqual(4711, list.First, "First item is wrong");
            Assert.AreEqual(4711, list.Last, "Last item is wrong");
        }

        [TestMethod]
        public void Given_empty_ConcurrentLinkedList_when_removing_first_and_last_item_Then_InvalidOperationException_is_thrown()
        {
            var list = new ConcurrentLinkedList<int>();
            Ensure.Throws<InvalidOperationException>(list.RemoveFirst, "Did not fail to remove first item");
            Ensure.Throws<InvalidOperationException>(list.RemoveLast, "Did not fail to remove last item");
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_removing_item_during_enumeration_Then_enumeration_still_sees_removed_item()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711, 42, 11 });
            var seenValues = new List<int>();
            foreach (var item in list)
            {
                if (item == 4711)
                {
                    list.RemoveLast();
                }

                seenValues.Add(item);
            }

            CollectionAssert.AreEqual(new[] {4711, 42, 11}, seenValues);
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_adding_item_during_enumeration_Then_enumeration_does_not_see_added_item()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711, 42, 11 });
            var seenValues = new List<int>();
            foreach (var item in list)
            {
                if (item == 42)
                {
                    list.AddLast(7);
                }

                seenValues.Add(item);
            }

            CollectionAssert.AreEqual(new[] { 4711, 42, 11 }, seenValues);
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_Then_SyncRoot_is_not_supported()
        {
            var list = new ConcurrentLinkedList<int>();
            Assert.IsFalse(list.IsSynchronized);
            Ensure.Throws<NotSupportedException>(() => list.SyncRoot.ToString());
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_copying_to_object_array_Then_all_items_are_copied()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711, 42, 11 });
            var array = new object[4];
            list.CopyTo(array, 1);
            CollectionAssert.AreEqual(new object[] { null, 4711, 42, 11 }, array);
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_When_copying_to_item_array_Then_all_items_are_copied()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711, 42, 11 });
            var array = new int[4];
            list.CopyTo(array, 1);
            CollectionAssert.AreEqual(new object[] { 0, 4711, 42, 11 }, array);
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_with_one_item_When_calling_TryAdd_Then_item_is_added_last()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711 });
            Assert.IsTrue(list.TryAdd(42), "TryAdd failed");
            Assert.AreEqual(2, list.Count, "Count is wrong");
            Assert.AreEqual(4711, list.First, "First item is wrong");
            Assert.AreEqual(42, list.Last, "Last item is wrong");
        }

        [TestMethod]
        public void Given_empty_ConcurrentLinkedList_When_calling_TryTake_Then_false_is_returned()
        {
            var list = new ConcurrentLinkedList<int>();
            int r;
            Assert.IsFalse(list.TryTake(out r));
        }

        [TestMethod]
        public void Given_ConcurrentLinkedList_with_two_items_When_calling_TryTake_Then_true_is_returned_and_first_item_removed()
        {
            var list = new ConcurrentLinkedList<int>(new[] { 4711, 42 });
            int r;
            Assert.IsTrue(list.TryTake(out r), "TryYake failed");
            Assert.AreEqual(1, list.Count, "Count is wrong");
            Assert.AreEqual(4711, r, "Taken value is wrong");
            Assert.AreEqual(42, list.First, "First item is wrong");
            Assert.AreEqual(42, list.Last, "Last item is wrong");
        }
    }
}
